
#include "JsonNode.h"
#include "util.h"

#include "JsonClassGenerator.h"

using namespace codegen;
using namespace resip;
using namespace std;

#define RESIPROCATE_SUBSYSTEM LogSystem::LOGMODULE


bool JsonNode::has_base_class() const
{
    return generator_->get_base_class_name().empty() ? false : true;
}

const resip::Data& JsonNode::get_base_class() const
{
    return generator_->get_base_class_name();
}

resip::Data JsonNode::str_type(bool use_alias /*= false*/) const
{
    if (has_base_class() && type_ == Node::VT_String)
    {
        return "ASString";
    }

    return Node::str_type(use_alias);
}

EncodeStream& JsonNode::encode_custom_function(EncodeStream& ostm) const
{
    ostm << LeadingIndent(depth_) << "protected:" << Symbols::LF;
    ostm << LeadingIndent(depth_ + 1) << "/// custom member functions" << Symbols::LF;

    if (parent_)
    {
        ostm << LeadingIndent(depth_ + 1) << "friend class " << str_parent_class_name() << ";" << Symbols::LF;
    }
    encode_function_pasre(ostm);
    encode_function_build(ostm);
    return ostm;
}

/*
/// non array node
    /// object type
        info_.parse_object(obj["info"]);
    /// common type
        obj.Get("MaxConf", max_conf_);
/// array node
    /// array element is object type
        int n_conf_count = obj["conf"].GetArraySize();
        for (int n_conf_i = 0; n_conf_i < n_conf_count; n_conf_i++)
        {
            conf* p_conf = new conf;
            p_conf->parse_object(obj["conf"][n_conf_i]);
            confs_.push_back(p_conf);
        }
    /// array element is common type
        int n_member_count = obj["member"].GetArraySize();
        for (int n_member_i = 0; n_member_i < n_member_count; n_member_i++)
        {
            ASString elem;
            obj["Member"].Get(n_member_i, elem);
            member_.push_back(elem);
        }
*/
EncodeStream& JsonNode::encode_function_pasre(EncodeStream& ostm) const
{
    ostm << LeadingIndent(depth_ + 1) << "void parse_object(neb::CJsonObject& obj)" << Symbols::LF
        << LeadingIndent(depth_ + 1) << "{" << Symbols::LF;
    for (auto child : childs_)
    {
        /// object-type
        if (child->isObject())
        {
            ostm << LeadingIndent(depth_ + 2) << child->str_name() << ".parse_object(obj[\"" << child->key_ << "\"]);" << Symbols::LF;
        }
        /// array-type
        else if (child->isArray())
        {
            ostm << LeadingIndent(depth_ + 2) << "int " << child->str_arr_size_name() << " = obj[\"" << child->key_ << "\"].GetArraySize();" << Symbols::LF
                << LeadingIndent(depth_ + 2) << "for (int " << child->str_arr_size_index_name() << " = 0; "
                << child->str_arr_size_index_name() << " < " << child->str_arr_size_name() << "; "
                << "++" << child->str_arr_size_index_name() << ")" << Symbols::LF
                << LeadingIndent(depth_ + 2) << "{" << Symbols::LF;
            const Node* arr_node = child->childs_.front();
            /// element is object-type
            if (arr_node->isObject())
            {
                ostm << LeadingIndent(depth_ + 3) << arr_node->str_type() << "* " << arr_node->str_com_tmp_name() << " = new " << arr_node->str_type() << ";" << Symbols::LF
                    << LeadingIndent(depth_ + 3) << arr_node->str_com_tmp_name() << "->parse_object(obj[\"" << arr_node->key_ << "\"][" << child->str_arr_size_index_name() << "]);" << Symbols::LF
                    << LeadingIndent(depth_ + 3) << child->str_name() << ".push_back(" << arr_node->str_com_tmp_name() << ");" << Symbols::LF;
            }
            /// element is common-type
            else
            {
                ostm << LeadingIndent(depth_ + 3) << arr_node->str_type() << " elem;" << Symbols::LF
                    << LeadingIndent(depth_ + 3) << "obj[\"" << child->key_ << "\"].Get(" << child->str_arr_size_index_name() << ", elem);" << Symbols::LF
                    << LeadingIndent(depth_ + 3) << child->str_name() << ".push_back(elem);" << Symbols::LF;
            }
            ostm << LeadingIndent(depth_ + 2) << "}" << Symbols::LF;
        }
        /// common-type
        else
        {
            ostm << LeadingIndent(depth_ + 2) << "obj.Get(\"" << child->key_ << "\", " << child->str_name() << ");" << Symbols::LF;
        }
    }
    ostm << LeadingIndent(depth_ + 1) << "}" << Symbols::LF;

    return ostm;
}


/*
/// object node
    neb::CJsonObject __Info;
    info_.buildObject(__Info);
    obj.Add("Info", __Info);
/// array node
    if (!confs_.empty())
    {
        neb::CJsonObject child;
        ConfContainer::const_iterator it = confs_.begin();
        for (; it != confs_.end(); ++it)
        {
        /// array element is object node
            neb::CJsonObject node;
            (*it)->buildObject(node);
            child.Add(node);
        /// array element is common node
            /// common-type is true/false
                child.Add(0, *it);
            /// common-type is non true/false
                child.Add(*it);
        }
        obj.Add("Conf", child);
    }
    else
    {
        obj.AddEmptySubArray("Conf");
    }
/// common node
    /// common-type is true/false
        obj.Add("Enabled", enabled_, true);
    /// common-type is non true/false
        obj.Add("MaxConf", max_conf_);
*/
EncodeStream& JsonNode::encode_function_build(EncodeStream& ostm) const
{
    ostm << LeadingIndent(depth_ + 1) << "void build_object(neb::CJsonObject& obj) const" << Symbols::LF
        << LeadingIndent(depth_ + 1) << "{" << Symbols::LF;
    for (auto child : childs_)
    {
        /// object-type
        if (child->isObject())
        {
            ostm << LeadingIndent(depth_ + 2) << "neb::CJsonObject " << child->str_com_tmp_name() << ";" << Symbols::LF
                << LeadingIndent(depth_ + 2) << child->str_name() << ".build_object(" << child->str_com_tmp_name() << ");" << Symbols::LF
                << LeadingIndent(depth_ + 2) << "obj.Add(\"" << child->key_ << "\", " << child->str_com_tmp_name() << ");" << Symbols::LF;
        }
        /// array-type
        else if (child->isArray())
        {
            ostm << LeadingIndent(depth_ + 2) << "if (!" << child->str_name() << ".empty())" << Symbols::LF
                << LeadingIndent(depth_ + 2) << "{" << Symbols::LF
                << LeadingIndent(depth_ + 3) << "neb::CJsonObject " << child->str_com_tmp_name() << ";" << Symbols::LF
                << LeadingIndent(depth_ + 3) << child->str_type(true) << "::const_iterator it = " << child->str_name() << ".begin();" << Symbols::LF
                << LeadingIndent(depth_ + 3) << "for (; it != " << child->str_name() << ".end(); ++it)" << Symbols::LF
                << LeadingIndent(depth_ + 3) << "{" << Symbols::LF;

            const Node* arr_node = child->childs_.front();
            /// element is object-type
            if (arr_node->isObject())
            {
                ostm << LeadingIndent(depth_ + 4) << "neb::CJsonObject _node;" << Symbols::LF
                    << LeadingIndent(depth_ + 4) << "(*it)->build_object(_node);" << Symbols::LF
                    << LeadingIndent(depth_ + 4) << child->str_com_tmp_name() << ".Add(_node);" << Symbols::LF;
            }
            /// element is common-type
            else
            {
                if (child->type_ == VT_Bool)
                {
                    ostm << LeadingIndent(depth_ + 4) << "obj.Add(0, *it);" << Symbols::LF;
                }
                else
                {
                    ostm << LeadingIndent(depth_ + 4) << "obj.Add(*it);" << Symbols::LF;
                }
            }

            ostm << LeadingIndent(depth_ + 3) << "}" << Symbols::LF
                << LeadingIndent(depth_ + 3) << "obj.Add(\"" << child->key_ << "\", " << child->str_com_tmp_name() << ");" << Symbols::LF
                << LeadingIndent(depth_ + 2) << "}" << Symbols::LF
                << LeadingIndent(depth_ + 2) << "else" << Symbols::LF
                << LeadingIndent(depth_ + 2) << "{" << Symbols::LF
                << LeadingIndent(depth_ + 3) << "obj.AddEmptySubArray(\"" << child->key_ << "\");" << Symbols::LF
                << LeadingIndent(depth_ + 2) << "}" << Symbols::LF;
        }
        /// common-type
        else
        {
            if (child->type_ == VT_Bool)
            {
                ostm << LeadingIndent(depth_ + 2) << "obj.Add(\"" << child->key_ << "\", " << child->str_name() << ", true);" << Symbols::LF;
            }
            else
            {
                ostm << LeadingIndent(depth_ + 2) << "obj.Add(\"" << child->key_ << "\", " << child->str_name() << ");" << Symbols::LF;
            }
        }
    }

    ostm << LeadingIndent(depth_ + 1) << "}" << Symbols::LF;

    return ostm;
}

//////////////////////////////////////////////////////////////////////////
EncodeStream& JsonNodeRoot::encode_class_begin(EncodeStream& ostm) const
{
    if (has_base_class())
    {
        ostm << LeadingIndent(depth_) << "class " << str_type() << " : public " << get_base_class() << Symbols::LF
            << LeadingIndent(depth_) << "{" << Symbols::LF;
    }
    else
    {
        Node::encode_class_begin(ostm);
    }
    return ostm;
}

EncodeStream& JsonNodeRoot::encode_class_ctor(EncodeStream& ostm) const
{
    if (has_base_class())
    {
        ostm << LeadingIndent(depth_ + 1) << str_type() << "(const ASString& seq) : " << get_base_class()
            // Can't confirm which enumeration type is used here, fill it with `JsonObject::TypeUnknown::<class_name>` so it can be replaced by regex
            << "(JsonObject::TypeUnknown::" << generator_->get_class_name() << ", seq)" << Symbols::LF
            << LeadingIndent(depth_ + 1) << "{" << Symbols::LF;
        for (auto node : childs_)
        {
            node->encode_member_init(ostm);
        }
        ostm << LeadingIndent(depth_ + 1) << "}" << Symbols::LF;

        ostm << LeadingIndent(depth_ + 1) << str_type() << "(JsonObject* base) : " << get_base_class() << "(base)" << Symbols::LF
            << LeadingIndent(depth_ + 1) << "{" << Symbols::LF
            << LeadingIndent(depth_ + 2) << "parse_object(object());" << Symbols::LF
            << LeadingIndent(depth_ + 1) << "}" << Symbols::LF;
    }
    else
    {
        ostm << LeadingIndent(depth_ + 1) << str_type() << "()" << Symbols::LF
            << LeadingIndent(depth_ + 1) << "{" << Symbols::LF;
        for (auto node : childs_)
        {
            node->encode_member_init(ostm);
        }
        ostm << LeadingIndent(depth_ + 1) << "}" << Symbols::LF;

        ostm << LeadingIndent(depth_ + 1) << str_type() << "(const std::string& strJson)" << Symbols::LF
            << LeadingIndent(depth_ + 1) << "{" << Symbols::LF
            << LeadingIndent(depth_ + 2) << "neb::CJsonObject obj;" << Symbols::LF
            << LeadingIndent(depth_ + 2) << "if(obj.Parse(strJson))" << Symbols::LF
            << LeadingIndent(depth_ + 2) << "{" << Symbols::LF
            << LeadingIndent(depth_ + 3) << "parse_object(obj);" << Symbols::LF
            << LeadingIndent(depth_ + 2) << "}" << Symbols::LF
            << LeadingIndent(depth_ + 1) << "}" << Symbols::LF;
    }

    return ostm;
}

EncodeStream& JsonNodeRoot::encode_custom_function(EncodeStream& ostm) const
{
    Data string_type("std::string");
    if (has_base_class())
        string_type = "ASString";

    ostm << LeadingIndent(depth_ + 1) << string_type << " to_string(bool formatted = false) const" << Symbols::LF
        << LeadingIndent(depth_ + 1) << "{" << Symbols::LF
        << LeadingIndent(depth_ + 2) << "neb::CJsonObject obj;" << Symbols::LF
        << LeadingIndent(depth_ + 2) << "build_object(obj);" << Symbols::LF
        << LeadingIndent(depth_ + 2) << "if (formatted)" << Symbols::LF
        << LeadingIndent(depth_ + 2) << "{" << Symbols::LF
        << LeadingIndent(depth_ + 3) << "return obj.ToFormattedString();" << Symbols::LF
        << LeadingIndent(depth_ + 2) << "}" << Symbols::LF
        << LeadingIndent(depth_ + 2) << "else" << Symbols::LF
        << LeadingIndent(depth_ + 2) << "{" << Symbols::LF
        << LeadingIndent(depth_ + 3) << "return obj.ToString();" << Symbols::LF
        << LeadingIndent(depth_ + 2) << "}" << Symbols::LF
        << LeadingIndent(depth_ + 1) << "}" << Symbols::LF;

    return JsonNode::encode_custom_function(ostm);
}


